package com.zktravel.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Thomason
 * @version 1.0
 * @since 2015-12-24 13:33
 */

public class HttpUtils {
	private static Logger logger = LoggerFactory.getLogger(HttpUtils.class);
	
	private static int CONNECTION_TIME_OUT = 2 * 60 * 1000;

	private static CloseableHttpClient httpClient;

	private static Lock lock = new ReentrantLock();

	private static CloseableHttpClient getHttpClient(Integer timeout) {
		if (httpClient != null) {
			return httpClient;
		}
		try {
			lock.lock();
			if (httpClient != null) {
				return httpClient;
			}
			HttpClientBuilder httpClientBuilder = HttpClients.custom();
			Integer poolSize = 200;
			poolSize = poolSize > 1000 ? 1000 : poolSize;//配置的线程数不能超过1000
			poolSize = poolSize < 10 ? 10 : poolSize;//配置的线程数不能低于100
			httpClientBuilder.setMaxConnTotal(poolSize);
			httpClientBuilder.setMaxConnPerRoute(poolSize);
			httpClientBuilder.disableCookieManagement();
			httpClientBuilder.disableAutomaticRetries();
			httpClientBuilder.setConnectionTimeToLive(60, TimeUnit.SECONDS);

			RequestConfig.Builder builder = RequestConfig.custom();
			if (timeout != null && timeout > 0) {
				builder.setConnectTimeout(timeout);
			} else {
				builder.setConnectTimeout(CONNECTION_TIME_OUT);
			}
			httpClientBuilder.setDefaultRequestConfig(builder.build());
			httpClientBuilder.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE);
			httpClientBuilder.disableAuthCaching();
			httpClientBuilder.disableCookieManagement();
			httpClientBuilder.disableRedirectHandling();
			httpClient = httpClientBuilder.build();
			return httpClient;
		} finally {
			lock.unlock();
		}
	}

	public static String sendGetRequest(RequestContext requestContext) throws IOException {
		CloseableHttpClient httpClient = getHttpClient(requestContext.getTimeout());
		String charset = requestContext.getCharset();
		String url = requestContext.getUrl();
		Map<String, String> headers = requestContext.getHeaders();
		if (requestContext.getParameters() != null) {
			StringBuilder builder = new StringBuilder();
			for (Map.Entry<String, String> entry : requestContext.getParameters().entrySet()) {
				String key = URLEncoder.encode(entry.getKey(), charset);
				String value = URLEncoder.encode(entry.getValue(), charset);
				builder.append("&").append(key).append("=").append(value);
			}
			if (url.contains("?")) {
				url += builder.toString();
			} else {
				url += "?" + builder.toString();
			}
		}
		HttpGet httpGet = new HttpGet(url);
		if (headers != null) {
			for (Map.Entry<String, String> entry : headers.entrySet()) {
				httpGet.addHeader(entry.getKey(), entry.getValue());
			}
		}
		CloseableHttpResponse response = httpClient.execute(httpGet);
		return getResponseString(url, response, charset);
	}

	private static String getResponseString(String url, CloseableHttpResponse response, String charset) throws IOException {
		try {
			if (response.getStatusLine().getStatusCode() != 200) {
				String error = EntityUtils.toString(response.getEntity(), charset);
				throw new RuntimeException("访问地址：" + url + "出错，错误代码：" + response.getStatusLine().getStatusCode() + "\n" + error);
			}
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				return EntityUtils.toString(entity, "UTF-8");
			}
			return null;
		} finally {
			EntityUtils.consumeQuietly(response.getEntity());
		}
	}

	public static String sendPostRequest(String url, Map<String, String> headers, Map<String, String> parameters) throws IOException {
		RequestContext requestContext = new RequestContext(url, headers, parameters);
		return sendPostRequest(requestContext);
	}

	public static String sendPostRequest(RequestContext requestContext) throws IOException {
		Integer timeout = requestContext.getTimeout();
		String url = requestContext.getUrl();
		Map<String, String> parameters = requestContext.getParameters();
		Map<String, String> headers = requestContext.getHeaders();
		String charset = requestContext.getCharset();
		CloseableHttpClient httpClient = getHttpClient(timeout);
		HttpPost httpPost = new HttpPost(url);
		List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
		if (parameters != null) {
			for (Map.Entry<String, String> entry : parameters.entrySet()) {
				nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
			}
		}
		if (headers != null) {
			for (Map.Entry<String, String> entry : headers.entrySet()) {
				httpPost.addHeader(entry.getKey(), entry.getValue());
			}
		}
		httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, charset));
		CloseableHttpResponse response = httpClient.execute(httpPost);
		return getResponseString(url, response, charset);
	}

	/*public static String sendBodyRequest(String url, Map<String, String> headers, String body) {
		return sendBodyRequest(url, headers, body, ContentType.APPLICATION_JSON);
	}*/


	public static String sendBodyRequest(RequestContext requestContext, ContentType contentType) {
		String url = requestContext.getUrl();
		Integer timeout = requestContext.getTimeout();
		Map<String, String> headers = requestContext.getHeaders();
		Object body = requestContext.getBody();
		Map<String, String> parameters = requestContext.getParameters();
		CloseableHttpClient httpClient = getHttpClient(timeout);

		if (parameters != null) {
			StringBuilder builder = new StringBuilder();
			for (Map.Entry<String, String> entry : parameters.entrySet()) {
				builder.append("&").append(entry.getKey()).append("=").append(entry.getValue());
			}
			builder.deleteCharAt(0);
			if (url.contains("?")) {
				url += "&" + builder.toString();
			} else {
				url += "?" + builder.toString();
			}
		}
		HttpPost httpPost = new HttpPost(url);
		StringEntity stringEntity = new StringEntity((String) body, contentType);
		httpPost.setEntity(stringEntity);
		if (headers != null) {
			for (Map.Entry<String, String> entry : headers.entrySet()) {
				httpPost.addHeader(entry.getKey(), entry.getValue());
			}
		}

		CloseableHttpResponse response = null;
		try {
			response = httpClient.execute(httpPost);
			return getResponseString(url, response, contentType.getCharset().name());
		} catch (IOException e) {
			logger.error("httpClient execute error", e);
		}
		
		return null;
	}

	public static String sendPostJsonRequest(String url, String json) {  
        CloseableHttpResponse response = null;
        RequestContext requestContext = new RequestContext(url);
    	CloseableHttpClient httpClient = getHttpClient(requestContext.getTimeout());
              
        //第二步：创建httpPost对象  
        HttpPost httpPost = new HttpPost(url);  
              
        //第三步：给httpPost设置JSON格式的参数  
        StringEntity requestEntity = new StringEntity(json,"utf-8");  
        requestEntity.setContentEncoding("UTF-8");                
        httpPost.setHeader("Content-type", "application/json");  
        httpPost.setEntity(requestEntity);  
             
        try {
			response = httpClient.execute(httpPost);
			return getResponseString(url, response, requestContext.getCharset());
		} catch (IOException e) {
			logger.error("httpClient execute error", e);
		}
		
		return null;
    } 
	
	public static byte[] downloadFileGet(RequestContext requestContext) throws IOException {
		Integer timeout = requestContext.getTimeout();
		String url = requestContext.getUrl();
		Map<String, String> headers = requestContext.getHeaders();
		Map<String, String> parameters = requestContext.getParameters();
		String charset = requestContext.getCharset();
		CloseableHttpClient httpClient = getHttpClient(timeout);
		HttpGet get = new HttpGet();
		if (headers != null) {
			for (Map.Entry<String, String> entry : headers.entrySet()) {
				get.addHeader(entry.getKey(), entry.getValue());
			}
		}
		if (parameters != null) {
			int i = 0;
			for (Map.Entry<String, String> entry : parameters.entrySet()) {
				String key = URLEncoder.encode(entry.getKey(), charset);
				String value = URLEncoder.encode(entry.getValue(), charset);
				if (i == 0) {
					url += "?" + key + "=" + value;
				} else {
					url += "&" + key + "=" + value;
				}
				i++;
			}
		}
		get.setURI(URI.create(url));
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		CloseableHttpResponse response = null;
		try {
			response = httpClient.execute(get);
			if (response.getStatusLine().getStatusCode() != 200) {
				String error = EntityUtils.toString(response.getEntity(), charset);
				throw new RuntimeException("访问地址：" + url + "出错，错误代码：" + response.getStatusLine().getStatusCode() + "\n" + error);
			}
			HttpEntity entity = response.getEntity();
			if (entity.isStreaming()) {
				entity.writeTo(stream);
			}
			return stream.toByteArray();
		} finally {
			IOUtils.closeQuietly(response);
			IOUtils.closeQuietly(stream);
		}
	}

	public static byte[] downloadFilePost(RequestContext requestContext) throws IOException {
		Map<String, String> parameters = requestContext.getParameters();
		String url = requestContext.getUrl();
		Map<String, String> headers = requestContext.getHeaders();
		Integer timeout = requestContext.getTimeout();
		String charset = requestContext.getCharset();
		CloseableHttpClient httpClient = getHttpClient(timeout);
		HttpPost httpPost = new HttpPost(url);
		List<NameValuePair> nvps = new ArrayList<NameValuePair>();
		if (parameters != null) {
			for (Map.Entry<String, String> entry : parameters.entrySet()) {
				nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
			}
		}
		if (headers != null) {
			for (Map.Entry<String, String> entry : headers.entrySet()) {
				httpPost.addHeader(entry.getKey(), entry.getValue());
			}
		}

		httpPost.setEntity(new UrlEncodedFormEntity(nvps, charset));
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		CloseableHttpResponse response = null;
		try {
			response = httpClient.execute(httpPost);
			if (response.getStatusLine().getStatusCode() != 200) {
				String error = EntityUtils.toString(response.getEntity(), "UTF-8");
				throw new RuntimeException("访问地址：" + url + "出错，错误代码：" + response.getStatusLine().getStatusCode() + "\n" + error);
			}
			HttpEntity entity = response.getEntity();
			if (entity.isStreaming()) {
				entity.writeTo(stream);
			}
			return stream.toByteArray();
		} finally {
			IOUtils.closeQuietly(response);
			IOUtils.closeQuietly(stream);
		}
	}

	public static String uploadFile(RequestContext requestContext) throws UnsupportedEncodingException {
		String url = requestContext.getUrl();
		Map<String, String> parameters = requestContext.getParameters();
		Map<String, String> headers = requestContext.getHeaders();
		Integer timeout = requestContext.getTimeout();
		String charset = requestContext.getCharset();
		Object body = requestContext.getBody();
		CloseableHttpClient httpClient = getHttpClient(timeout);
		StringBuilder queryString = new StringBuilder();
		if (parameters != null) {
			for (Map.Entry<String, String> entry : parameters.entrySet()) {
				queryString.append("&");
				queryString.append(URLEncoder.encode(entry.getKey(), charset)).append("=").append(URLEncoder.encode(entry.getValue(), charset));
			}
		}
		if (url.contains("?")) {
			url += queryString.toString();
		} else {
			url += "?" + queryString.toString();
		}
		HttpPost post = new HttpPost(url);
		if (headers != null) {
			for (Map.Entry<String, String> entry : headers.entrySet()) {
				post.addHeader(entry.getKey(), entry.getValue());
			}
		}
		MultipartEntity multipartEntity = new MultipartEntity();
		multipartEntity.addPart("file", new ByteArrayBody((byte[]) body, "file"));
		post.setEntity(multipartEntity);
		CloseableHttpResponse response = null;
		try {
			response = httpClient.execute(post);
			return getResponseString(url, response, "UTF-8");
		} catch (IOException e) {
			logger.error("httpClient execute error", e);
		} finally {
			IOUtils.closeQuietly(response);
		}
		return null;
	}

	public static class RequestContext {
		private String url;
		private Map<String, String> headers;
		private Map<String, String> parameters;
		private Object body;
		private Integer timeout;
		private String charset = "UTF-8";

		public RequestContext() {
		}

		public RequestContext(String url) {
			this.url = url;
		}

		public RequestContext(String url, Map<String, String> headers, Map<String, String> parameters) {
			this.url = url;
			this.headers = headers;
			this.parameters = parameters;
		}

		public RequestContext(String url, Map<String, String> headers, Map<String, String> parameters, Object body) {
			this.url = url;
			this.headers = headers;
			this.parameters = parameters;
			this.body = body;
		}

		public RequestContext(String url, Map<String, String> headers, Map<String, String> parameters, Object body, Integer timeout) {
			this.url = url;
			this.headers = headers;
			this.parameters = parameters;
			this.body = body;
			this.timeout = timeout;
		}

		public RequestContext(String url, Map<String, String> headers, Map<String, String> parameters, Object body, Integer timeout, String charset) {
			this.url = url;
			this.headers = headers;
			this.parameters = parameters;
			this.body = body;
			this.timeout = timeout;
			this.charset = charset;
		}

		public String getUrl() {
			return url;
		}

		public void setUrl(String url) {
			this.url = url;
		}

		public Map<String, String> getHeaders() {
			return headers;
		}

		public void setHeaders(Map<String, String> headers) {
			this.headers = headers;
		}

		public Map<String, String> getParameters() {
			return parameters;
		}

		public void setParameters(Map<String, String> parameters) {
			this.parameters = parameters;
		}

		public Object getBody() {
			return body;
		}

		public void setBody(Object body) {
			this.body = body;
		}

		public Integer getTimeout() {
			return timeout;
		}

		public void setTimeout(Integer timeout) {
			this.timeout = timeout;
		}

		public String getCharset() {
			return charset;
		}

		public void setCharset(String charset) {
			this.charset = charset;
		}
	}
}